use std::time::Duration;

use serde::Deserialize;

use crate::{
    connection::{
        cluster::ClusterConnection, standalone::StandaloneConnection, Connection, ConnectionState,
    },
    resp::Command,
    RedisResult,
};

pub(crate) struct RedisOptions {
    pub(crate) server_mode: RedisServerMode,
    pub(crate) user: Option<String>,
    pub(crate) client_name: Option<String>,
    /// the password
    pub(crate) password: Option<String>,
    pub(crate) timeout: Option<Duration>,
    pub(crate) connect_timeout: Duration,
    pub(crate) reconnect_interval: Duration,
    pub(crate) receive_timeout: Duration,
    pub(crate) retry_on_send_error: u8,
    // send_timeout: Duration,
}

pub enum RedisServerMode {
    Standalone(String, u32),
    Sentinel,
    Cluster(Vec<(String, u32)>),
}

pub struct RedisClientBuilder {
    server_mode: RedisServerMode,
    user: Option<String>,
    client_name: Option<String>,
    /// the password
    password: Option<String>,
    timeout: Option<Duration>,
    connect_timeout: Option<Duration>,
    reconnect_interval: Option<Duration>,
    receive_timeout: Option<Duration>,
    // send_timeout: Option<Duration>,
    is_auto_connect: bool,
    retry_on_send_error: u8,
}

impl RedisClientBuilder {
    pub fn new_standalone(host: impl Into<String>, port: u32) -> Self {
        Self::new(RedisServerMode::Standalone(host.into(), port))
    }

    pub fn new_sentinel() -> Self {
        Self::new(RedisServerMode::Sentinel)
    }

    pub fn new_cluster(hosts: Vec<(String, u32)>) -> Self {
        Self::new(RedisServerMode::Cluster(hosts))
    }

    pub fn new(server_mode: RedisServerMode) -> Self {
        Self {
            server_mode,
            user: None,
            password: None,
            client_name: None,
            timeout: None,
            connect_timeout: None,
            reconnect_interval: None,
            receive_timeout: None,
            // send_timeout: None,
            is_auto_connect: true,
            retry_on_send_error: 0,
        }
    }

    /// set user, default is `default`
    pub fn user(mut self, user: impl Into<String>) -> Self {
        self.user = Some(user.into());
        self
    }

    /// set the user password
    pub fn password(mut self, password: impl Into<String>) -> Self {
        self.password = Some(password.into());
        self
    }

    /// set the server timeout
    /// 
    /// if the timeout value in server is 60, suggest to set this value which less than 60, such as 55
    /// 
    /// if the timeout value in server is 0, that's means no timeout, this value shouldn't be set
    pub fn timeout(mut self, timeout: Duration) -> Self {
        self.timeout = Some(timeout);
        self
    }

    pub fn connect_timeout(mut self, connect_timeout: Duration) -> Self {
        self.connect_timeout = Some(connect_timeout);
        self
    }

    pub fn reconnect_interval(mut self, reconnect_interval: Duration) -> Self {
        self.reconnect_interval = Some(reconnect_interval);
        self
    }

    pub fn receive_timeout(mut self, receive_timeout: Duration) -> Self {
        self.receive_timeout = Some(receive_timeout);
        self
    }

    // pub fn send_timeout(mut self, send_timeout: Duration) -> Self {
    //     self.send_timeout = Some(send_timeout);
    //     self
    // }

    pub fn auto_connect(mut self, is_auto_connect: bool) -> Self {
        self.is_auto_connect = is_auto_connect;
        self
    }

    pub fn client_name(mut self, client_name: String) -> Self {
        self.client_name = Some(client_name);
        self
    }

    pub fn retry_on_send_error(mut self, retry_on_send_error: u8) -> Self {
        self.retry_on_send_error = retry_on_send_error;
        self
    }

    pub async fn build_and_connect(self) -> RedisResult<RedisClient> {
        RedisClient::new(RedisOptions {
            server_mode: self.server_mode,
            user: self.user,
            client_name: self.client_name,
            password: self.password,
            timeout: self.timeout,
            connect_timeout: self.connect_timeout.unwrap_or(Duration::from_secs(10)),
            reconnect_interval: self.reconnect_interval.unwrap_or(Duration::from_secs(5)),
            receive_timeout: self.receive_timeout.unwrap_or(Duration::from_secs(15)),
            retry_on_send_error: self.retry_on_send_error,
            // send_timeout: self.send_timeout.unwrap_or(Duration::from_secs(30)),
        })
        .await
    }
}

pub struct RedisClient {
    pub(crate) connection: Connection,
}

impl RedisClient {
    async fn new(options: RedisOptions) -> RedisResult<Self> {
        let connection = {
            match &options.server_mode {
                RedisServerMode::Standalone(host, ip) => {
                    let connection_inner = StandaloneConnection::new(
                        host.to_string(),
                        *ip,
                        ConnectionState::new(options),
                    );
                    connection_inner.connect().await?;
                    Connection::Standalone(connection_inner)
                }
                RedisServerMode::Cluster(hosts) => {
                    let connection_inner =
                        ClusterConnection::new(hosts.clone(), ConnectionState::new(options))
                            .await?;
                    Connection::Cluster(connection_inner)
                }
                RedisServerMode::Sentinel => todo!(),
            }
        };
        Ok(Self { connection })
    }

    #[inline]
    pub(crate) async fn send_command<'a, T: Deserialize<'a>>(
        &self,
        cmd: Command<'a>,
    ) -> RedisResult<T> {
        match &self.connection {
            crate::connection::Connection::Standalone(connection) => {
                connection.send_command::<T>(cmd).await
            }
            crate::connection::Connection::Cluster(connection) => {
                connection.send_command::<T>(cmd).await
            }
            crate::connection::Connection::Sentinel => todo!(),
        }
    }
}
